//////////////////////////////////////////////////////////////////////////////
//	Copyright 2011 Alex Leffelman
//	
//	Licensed under the Apache License, Version 2.0 (the "License");
//	you may not use this file except in compliance with the License.
//	You may obtain a copy of the License at
//	
//	http://www.apache.org/licenses/LICENSE-2.0
//	
//	Unless required by applicable law or agreed to in writing, software
//	distributed under the License is distributed on an "AS IS" BASIS,
//	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//	See the License for the specific language governing permissions and
//	limitations under the License.
//////////////////////////////////////////////////////////////////////////////

package com.zhuxulin.midi;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import com.zhuxulin.midi.util.MidiUtil;

public class MidiFile {
	/**
	 * Mid file head length
	 */
	public static final int HEADER_SIZE = 14;
	/**
	 * Mid file head flag
	 */
	public static final byte[] IDENTIFIER = { 'M', 'T', 'h', 'd' };
	/**
	 * Default mid base tick
	 */
	public static final int DEFAULT_RESOLUTION = 480;
	/**
	 * Mid type single track and so on
	 */
	private int mType;
	/**
	 * Mid track number
	 */
	private int mTrackCount;
	/**
	 * Mid ticks default
	 */
	private int mResolution;

	private ArrayList<MidiTrack> mTracks;

	public MidiFile() {
		this(DEFAULT_RESOLUTION);
	}

	public MidiFile(int resolution) {
		this(resolution, new ArrayList<MidiTrack>());
	}

	public MidiFile(int resolution, ArrayList<MidiTrack> tracks) {

		mResolution = resolution >= 0 ? resolution : DEFAULT_RESOLUTION;

		mTracks = tracks != null ? tracks : new ArrayList<MidiTrack>();
		mTrackCount = tracks.size();
		mType = mTrackCount > 1 ? 1 : 0;
	}

	public MidiFile(File fileIn) throws FileNotFoundException, IOException {
		this(new FileInputStream(fileIn));
	}

	public MidiFile(InputStream rawIn) throws IOException {

		BufferedInputStream in = new BufferedInputStream(rawIn);

		byte[] buffer = new byte[HEADER_SIZE];
		in.read(buffer);

		initFromBuffer(buffer);

		mTracks = new ArrayList<MidiTrack>();
		for (int i = 0; i < mTrackCount; i++) {
			mTracks.add(new MidiTrack(in));
		}
	}

	public void setType(int type) {
		if (type < 0) {
			type = 0;
		} else if (type > 2) {
			type = 1;
		} else if (type == 0 && mTrackCount > 1) {
			type = 1;
		}
		mType = type;
	}

	public int getType() {
		return mType;
	}

	public int getTrackCount() {
		return mTrackCount;
	}

	public void setResolution(int res) {
		if (res >= 0) {
			mResolution = res;
		}
	}

	public int getResolution() {
		return mResolution;
	}

	/**
	 * Get the longest tract's length
	 * 
	 * @return
	 */
	public long getLengthInTicks() {
		long length = 0;
		for (MidiTrack T : mTracks) {
			long l = T.getLengthInTicks();
			if (l > length) {
				length = l;
			}
		}
		return length;
	}

	public ArrayList<MidiTrack> getTracks() {
		return mTracks;
	}

	public void addTrack(MidiTrack T) {
		addTrack(T, mTracks.size());
	}

	public void addTrack(MidiTrack T, int pos) {

		if (pos > mTracks.size()) {
			pos = mTracks.size();
		} else if (pos < 0) {
			pos = 0;
		}

		mTracks.add(pos, T);
		mTrackCount = mTracks.size();
		mType = mTrackCount > 1 ? 1 : 0;
	}

	public void removeTrack(int pos) {
		if (pos < 0 || pos >= mTracks.size()) {
			return;
		}
		mTracks.remove(pos);
		mTrackCount = mTracks.size();
		mType = mTrackCount > 1 ? 1 : 0;
	}

	/**
	 * Write Midi code to file
	 * 
	 * @param outFile
	 * @throws FileNotFoundException
	 * @throws IOException
	 */
	public void writeToFile(File outFile) throws FileNotFoundException,
			IOException {

		FileOutputStream fout = new FileOutputStream(outFile);

		fout.write(IDENTIFIER);
		fout.write(MidiUtil.intToBytes(6, 4));
		fout.write(MidiUtil.intToBytes(mType, 2));
		fout.write(MidiUtil.intToBytes(mTrackCount, 2));
		fout.write(MidiUtil.intToBytes(mResolution, 2));

		for (MidiTrack T : mTracks) {
			T.writeToFile(fout);
		}

		fout.flush();
		fout.close();
	}

	/**
	 * Verify the mid file head,where it is MThd</br> if it is not a mid file,it
	 * will exit
	 * 
	 * @param buffer
	 */
	private void initFromBuffer(byte[] buffer) {
		if (!MidiUtil.bytesEqual(buffer, IDENTIFIER, 0, 4)) {
			System.out.println("File identifier not MThd. Exiting");
			mType = 0;
			mTrackCount = 0;
			mResolution = DEFAULT_RESOLUTION;
			return;
		}

		mType = MidiUtil.bytesToInt(buffer, 8, 2);
		mTrackCount = MidiUtil.bytesToInt(buffer, 10, 2);
		mResolution = MidiUtil.bytesToInt(buffer, 12, 2);
	}
}
